#include "RenderSystemGLES.h"
#include "ContextEGL1_1.h"
#include "ContextEGL2.h"
#include "Texture.h"
#include "Material.h"
#include "RenderModelManager.h"
#include "../framework/FileSystem.h"
#include <malloc.h>

static bool R_AllocScreenCaptureBuffer(const int Width, const int Height, unsigned char **pLines)
{
    unsigned char	*pLines2;
    unsigned char   *pBuf;
    int				i, j;


    /* Allocate memory for line */
    pBuf = *pLines= (unsigned char *)malloc(Width*Height*4 * sizeof(unsigned char));
    if (!(*pLines)) return false;


    /* Allocate memory for line */
    pLines2 = (unsigned char *)malloc(4 * Width * Height * sizeof(unsigned char));
    if (!pLines2) return false;

    eglWaitGL();

    while (glGetError());
    /* Read line from frame buffer */
    glReadPixels(0, 0, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, pLines2);

    if(glGetError())
    {
        return false;
    }
    else
    {
        /* Convert RGB to BGR in line */
        for (j = 0, i = 0; j < 4 * Width * Height; j += 4, i += 4)
        {
            pBuf[i] = pLines2[j];
            pBuf[i+1] = pLines2[j+1];
            pBuf[i+2] = pLines2[j+2];
            pBuf[i+3] = pLines2[j+3];
        }
    }

    free(pLines2);

    return true;
}

static void R_WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical ) {
    byte	*buffer;
    int		i;
    int		bufferSize = width*height*4 + 18;
    int     imgStart = 18;

    buffer = (byte *)malloc( bufferSize );
    memset( buffer, 0, 18 );
    buffer[2] = 2;		// uncompressed type
    buffer[12] = width&255;
    buffer[13] = width>>8;
    buffer[14] = height&255;
    buffer[15] = height>>8;
    buffer[16] = 32;	// pixel size
    if ( !flipVertical ) {
        buffer[17] = (1<<5);	// flip bit, for normal top to bottom raster order
    }

    // swap rgb to bgr
    for ( i=imgStart ; i<bufferSize ; i+=4 ) {
        buffer[i] = data[i-imgStart+2];		// blue
        buffer[i+1] = data[i-imgStart+1];		// green
        buffer[i+2] = data[i-imgStart+0];		// red
        buffer[i+3] = data[i-imgStart+3];		// alpha
    }

    fileSystem->WriteFile( filename, buffer, bufferSize );

    free(buffer);
}

RenderSystemGLES tr;
RenderSystem*   renderSystem = &tr;

RenderSystemGLES::RenderSystemGLES()
{
    initalized = false;
}

RenderSystemGLES::~RenderSystemGLES()
{
}

void RenderSystemGLES::Init( RWndHandle hWnd, const RInitParams& params, RRendererType type )
{
    this->type = type;
    frameCounter = 0;

    contexts.clear();

    CreateContext( hWnd, params );

    textureManager->Init();
    materialManager->Init();
    materialManager->ImportMaterialFromDeclManager();
    modelManager->Init();

    primaryWorld = NULL;
    initalized = true;
}

void RenderSystemGLES::Shutdown()
{
    modelManager->Shutdown();
    materialManager->Shutdown();
    textureManager->Shutdown();

    if( initalized ) {
        ReleaseAllRenderWorld();
        ReleaseAllContext();
    }

    frameCounter = 0;
    initalized = false;
}

bool RenderSystemGLES::IsInitialized() const
{
    return initalized;
}

RRendererType RenderSystemGLES::GetType() const
{
    return type;
}

const glConfig& RenderSystemGLES::GetConfig() const {
    return config;
}

int RenderSystemGLES::NumContexts() const
{
    return contexts.size();
}

bool RenderSystemGLES::CreateContext( RWndHandle hWnd, const RInitParams& params )
{
    ContextGL* pNew = NULL;
    switch( type )
    {
    case RT_GLES1_1:
        pNew = new ContextEGL1_1;
        break;
    case RT_GLES2:
        pNew = new ContextEGL2;
        break;
    }

    if( !pNew->Init( hWnd, params.width, params.height, params.bpp, params.multiSample ) )
    {
        inf::fatal_error( "Failed to create context in RenderSystemGLES" );
        delete pNew;
        return false;
    }

    contexts.set( hWnd, pNew );
    currentContext = pNew;
    hWndHandle = hWnd;

    DefaultStatus();
    SetupConfig();
    return true;
}

void RenderSystemGLES::ReleaseContext( RWndHandle hWnd )
{
    ContextGLHash::iterator found = contexts.find( hWnd );
    if( found == contexts.end() ) {
        // not exist
        return;
    }

    delete found->second;
    contexts.remove( hWnd );

    if( hWnd == hWndHandle ) {
        hWndHandle = NULL;
    }
}

RWndHandle RenderSystemGLES::GetCurrentContext() const
{
    return hWndHandle;
}

void RenderSystemGLES::MakeCurrentContext()
{
    assert( currentContext );

    currentContext->MakeCurrent();
}

void RenderSystemGLES::ReleaseAllContext()
{
    ContextGLHash::iterator it = contexts.begin();
    for( ; it != contexts.end(); ++it ) {
        if( it && it->second ) {
            it->second->Release();
            delete it->second;
        }
    }

    contexts.clear();
}



void RenderSystemGLES::SelectContext( RWndHandle hWnd )
{
    ContextGLHash::const_iterator found = contexts.find( hWnd );
#ifdef INF_DEBUG
    if( found == contexts.end() ) {
        inf::warning( "Invalid render window handle: Oct(%d)", hWnd );
        return;
    }
#endif
    currentContext = found->second;

    hWndHandle = hWnd;
}

bigInt_t RenderSystemGLES::NumberFrame() const
{
    return frameCounter;
}

void RenderSystemGLES::PresentFrame()
{
    currentContext->PresentContext();
    ++frameCounter;
}

void RenderSystemGLES::BeginFrame( int windowWidth, int windowHeight )
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

}

void RenderSystemGLES::EndFrame( int *frontEndMsec /*= NULL*/, int *backEndMsec /*= NULL */ )
{
    glFlush();
}

void            RenderSystemGLES::ReleaseAllRenderWorld() {
    inf::delete_contents( worlds, false );
    worlds.clear();

    primaryWorld = NULL;
}

RenderWorld * RenderSystemGLES::AllocRenderWorld()
{
    RenderWorldLocal* world = new RenderWorldLocal;

    worlds.append( world );
    return world;
}

void        RenderSystemGLES::SetPrimaryWorld( RenderWorld* world ) {
    primaryWorld = static_cast<RenderWorldLocal*>( world );
}

RenderWorld * RenderSystemGLES::GetPrimaryWorld() const
{
    return primaryWorld;
}

void RenderSystemGLES::DefaultStatus()
{
    // Sets the clear color
    glClearColor(0.6f, 0.8f, 1.0f, 1.0f);

    // Enables texturing
    glEnable(GL_TEXTURE_2D);

    // Enable the depth test
    glEnable(GL_DEPTH_TEST);

    // Enable culling
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);

    glShadeModel(GL_SMOOTH);

    glEnable(GL_LIGHTING);
}

void RenderSystemGLES::SetupConfig() {
    glGetIntegerv( GL_MAX_TEXTURE_UNITS, &config.maxTextureUnit );
    glGetIntegerv( GL_MAX_TEXTURE_SIZE, &config.maxTextureSize );
}

void RenderSystemGLES::TakeScreenshot( const char *fileName )
{
    unsigned char* imgData = NULL;
    if( R_AllocScreenCaptureBuffer( currentContext->GetWidth(), currentContext->GetHeight(), &imgData ) ) {
        R_WriteTGA( fileName, imgData, currentContext->GetWidth(), currentContext->GetHeight(), true );

        free( imgData );

        common->Printf( "TakScreenshot: %s\n", fileName );

    }
}
